﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;

namespace SharpSoft.WPF
{
    /// <summary>
    /// 程序集资源辅助方法
    /// </summary>
    public class ResourceHelper
    {
        /// <summary>
        /// 获取程序集资源文件夹中的资源数据流
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="folder"></param>
        /// <returns></returns>
        public static Dictionary<string, Stream> GetResourceStreams(Assembly assembly, string folder)
        {
            var allresnames = assembly.GetManifestResourceNames();
            Dictionary<string, Stream> sorted = new Dictionary<string, Stream>();
            foreach (string name in allresnames)
            {
                Dictionary<string, Stream> dic = new Dictionary<string, Stream>();
                Stream st = assembly.GetManifestResourceStream(name);
                try
                {
                    ResourceReader resourceReader;
                    try
                    {
                        resourceReader = new ResourceReader(st);
                    }
                    catch (Exception)
                    {//不是有效的项目资源
                        continue;
                    }
                    using (resourceReader)
                    {
                        foreach (DictionaryEntry resourceEntry in resourceReader)
                        {
                            if (resourceEntry.Key is string relpath)
                            {
                                if (resourceEntry.Value is Stream stream)
                                {
                                    var f = System.IO.Path.GetDirectoryName(relpath).Replace("\\", "/");
                                    Uri u1 = new Uri(f, UriKind.Relative);
                                    if (folder.StartsWith("/"))
                                    {//去掉头部的斜杠
                                        folder = folder.TrimStart('/');
                                    }
                                    var u2 = new Uri(folder, UriKind.Relative);
                                    if (Uri.Compare(u1, u2, UriComponents.Path, UriFormat.UriEscaped, StringComparison.OrdinalIgnoreCase) == 0)
                                    {
                                        dic.Add(relpath, stream);
                                    }
                                }
                            }
                        }
                    }
                }
                catch (ArgumentException ex)
                {
                    throw new Exception("读取资源清单出错。", ex);
                }
                string[] keys = new string[dic.Count];
                var i = 0;
                foreach (var item in dic.Keys)
                {
                    keys[i] = item;
                    i++;
                }
                Array.Sort(keys);//使用资源名进行排序
                foreach (var key in keys)
                {
                    sorted.Add(key, dic[key]);
                }


            }
            return sorted;
        }
        /// <summary>
        /// 异步获取程序集资源文件夹中的资源数据流
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="folder"></param>
        /// <returns></returns>
        public static async Task<Dictionary<string, Stream>> GetResourceGetResourceStreamsAsync(Assembly assembly, string folder)
        {
            return await Task.Run<Dictionary<string, Stream>>(
                () => GetResourceStreams(assembly, folder)
                );

        }
        /// <summary>
        /// 获取资源文件夹内的所有图片（文件夹内不能有其他类型的文件）
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="folder"></param>
        /// <returns></returns>
        public static async Task<ImageSource[]> GetResourceImagesAsync(Assembly assembly, string folder)
        {
            var dic = await GetResourceGetResourceStreamsAsync(assembly, folder);
            List<ImageSource> list = new List<ImageSource>(dic.Count);
            foreach (var stream in dic.Values)
            {
                var bmp = await BitmapHelper.LoadAsync(stream);
                list.Add(bmp);
            }
            return list.ToArray();
        }

        /// <summary>
        /// 获取程序集中某个目录下“资源”的Uri列表
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="folder"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static Uri[] GetResourceUriList(Assembly assembly, string folder)
        {
            var allresnames = assembly.GetManifestResourceNames();
            List<string> dic = new List<string>();
            foreach (string name in allresnames)
            {
                Stream st = assembly.GetManifestResourceStream(name);
                try
                {
                    ResourceReader resourceReader;
                    try
                    {
                        resourceReader = new ResourceReader(st);
                    }
                    catch (Exception)
                    {//不是有效的项目资源
                        continue;
                    }
                    using (resourceReader)
                    {
                        foreach (DictionaryEntry resourceEntry in resourceReader)
                        {
                            if (resourceEntry.Key is string relpath)
                            {
                                if (resourceEntry.Value is Stream stream)
                                {
                                    var f = System.IO.Path.GetDirectoryName(relpath).Replace("\\", "/");
                                    Uri u1 = new Uri(f, UriKind.Relative);
                                    if (folder.StartsWith("/"))
                                    {//去掉头部的斜杠
                                        folder = folder.TrimStart('/');
                                    }
                                    var u2 = new Uri(folder, UriKind.Relative);
                                    if (Uri.Compare(u1, u2, UriComponents.Path, UriFormat.UriEscaped, StringComparison.OrdinalIgnoreCase) == 0)
                                    {
                                        dic.Add(relpath);
                                    }
                                }
                            }
                        }
                    }
                }
                catch (ArgumentException ex)
                {
                    throw new Exception("读取资源清单出错。", ex);
                }
            }
            dic.Sort();
            List<Uri> list = new List<Uri>();
            foreach (var item in dic)
            {
                list.Add(new Uri($"pack://application:,,,/{assembly.GetName().Name};component/{item}", UriKind.Absolute));
            }
            return list.ToArray();
        }
        #region URI相关
        // ******************************************************************
        /// <summary>
        /// The resource should be defined as 'Resource' not as 'Embedded resource'.
        /// </summary>
        /// <param name="resourcePath">The resource path</param>
        /// <param name="assembly">If null, then use calling assembly to find the resource</param>
        /// <returns></returns>
        public static Uri GetLocationUri(string resourcePath, Assembly assembly = null)
        {
            if (assembly == null)
            {
                assembly = Assembly.GetCallingAssembly();
            }

            resourcePath = resourcePath.Replace('\\', '/');

            return new Uri(@"pack://application:,,,/" + assembly.GetName().Name + ";component" +
                (resourcePath[0] == '/' ? resourcePath : "/" + resourcePath), UriKind.Absolute);
        }


        public static Assembly GetAssemblyByUri(Uri uri)
        {
            if (uri.Authority.StartsWith("application") && uri.Scheme == "pack")
            {
                string localPath = uri.GetComponents(UriComponents.Path, UriFormat.UriEscaped);

                int indexLocalPathWithoutAssembly = localPath.IndexOf(";component/");
                if (indexLocalPathWithoutAssembly == -1)
                {
                    indexLocalPathWithoutAssembly = 0;
                }
                else
                {
                    indexLocalPathWithoutAssembly += 11;
                }


                if (uri.Segments.Length > 1)
                {
                    if (uri.Segments[0] == "/" && uri.Segments[1].EndsWith(";component/"))
                    {
                        int index = uri.Segments[1].IndexOf(";");
                        if (index > 0)
                        {
                            string assemblyName = uri.Segments[1].Substring(0, index);
                            var assname = Uri.UnescapeDataString(assemblyName);
                            foreach (Assembly asmIter in AppDomain.CurrentDomain.GetAssemblies())
                            {
                                if (asmIter.GetName().Name == assname)
                                {
                                    return asmIter;
                                }
                            }
                        }
                    }
                }
                else
                {
                    return Assembly.GetCallingAssembly();
                }

            }
            return null;
        }

        // ******************************************************************
        /// <summary>
        /// Will load resource from any assembly that is part of the application.
        /// It does not rely on Application which is specific to a (UI) frameowrk.
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="asm"></param>
        /// <returns></returns>
        public static Stream LoadResourceFromUri(Uri uri, Assembly asm = null)
        {
            Stream stream = null;

            if (uri.Authority.StartsWith("application") && uri.Scheme == "pack")
            {
                string localPath = uri.GetComponents(UriComponents.Path, UriFormat.UriEscaped);

                int indexLocalPathWithoutAssembly = localPath.IndexOf(";component/");
                if (indexLocalPathWithoutAssembly == -1)
                {
                    indexLocalPathWithoutAssembly = 0;
                }
                else
                {
                    indexLocalPathWithoutAssembly += 11;
                }

                if (asm != null) // Take the provided assembly, do not check for the asm in the uri.
                {
                    stream = GetAssemblyResourceStream(asm, localPath.Substring(indexLocalPathWithoutAssembly));
                }
                else
                {
                    if (uri.Segments.Length > 1)
                    {
                        if (uri.Segments[0] == "/" && uri.Segments[1].EndsWith(";component/"))
                        {
                            int index = uri.Segments[1].IndexOf(";");
                            if (index > 0)
                            {
                                string assemblyName = uri.Segments[1].Substring(0, index);

                                foreach (Assembly asmIter in AppDomain.CurrentDomain.GetAssemblies())
                                {
                                    if (asmIter.GetName().Name == assemblyName)
                                    {
                                        stream = GetAssemblyResourceStream(asmIter, localPath.Substring(indexLocalPathWithoutAssembly));
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    if (stream == null)
                    {
                        asm = Assembly.GetCallingAssembly();
                        stream = GetAssemblyResourceStream(asm, localPath.Substring(indexLocalPathWithoutAssembly));
                    }
                }
            }
            return stream;
        }

        // ******************************************************************
        /// <summary>
        /// The path separator is '/'.  The path should not start with '/'.
        /// </summary>
        /// <param name="asm"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public static Stream GetAssemblyResourceStream(Assembly asm, string path)
        {
            // Just to be sure
            if (path[0] == '/')
            {
                path = path.Substring(1);
            }

            // Just to be sure
            if (path.IndexOf('\\') == -1)
            {
                path = path.Replace('\\', '/');
            }

            Stream resStream = null;

            string resName = asm.GetName().Name + ".g.resources"; // Ref: Thomas Levesque Answer at:
            // http://stackoverflow.com/questions/2517407/enumerating-net-assembly-resources-at-runtime

            using (var stream = asm.GetManifestResourceStream(resName))
            {
                using (var resReader = new System.Resources.ResourceReader(stream))
                {
                    string dataType = null;
                    byte[] data = null;
                    try
                    {
                        resReader.GetResourceData(path.ToLower(), out dataType, out data);
                    }
                    catch (Exception ex)
                    {
                        DebugPrintResources(resReader);
                    }

                    if (data != null)
                    {
                        switch (dataType) // COde from 
                        {
                            // Handle internally serialized string data (ResourceTypeCode members).
                            case "ResourceTypeCode.String":
                                BinaryReader reader = new BinaryReader(new MemoryStream(data));
                                string binData = reader.ReadString();
                                Console.WriteLine("   Recreated Value: {0}", binData);
                                break;
                            case "ResourceTypeCode.Int32":
                                Console.WriteLine("   Recreated Value: {0}", BitConverter.ToInt32(data, 0));
                                break;
                            case "ResourceTypeCode.Boolean":
                                Console.WriteLine("   Recreated Value: {0}", BitConverter.ToBoolean(data, 0));
                                break;
                            // .jpeg image stored as a stream.
                            case "ResourceTypeCode.Stream":
                                ////const int OFFSET = 4;
                                ////int size = BitConverter.ToInt32(data, 0);
                                ////Bitmap value1 = new Bitmap(new MemoryStream(data, OFFSET, size));
                                ////Console.WriteLine("   Recreated Value: {0}", value1);

                                const int OFFSET = 4;
                                resStream = new MemoryStream(data, OFFSET, data.Length - OFFSET);

                                break;
                            // Our only other type is DateTimeTZI.
                            default:
                                ////// No point in deserializing data if the type is unavailable.
                                ////if (dataType.Contains("DateTimeTZI") && loaded)
                                ////{
                                ////    BinaryFormatter binFmt = new BinaryFormatter();
                                ////    object value2 = binFmt.Deserialize(new MemoryStream(data));
                                ////    Console.WriteLine("   Recreated Value: {0}", value2);
                                ////}
                                ////break;
                                break;
                        }

                        // resStream = new MemoryStream(resData);
                    }
                }
            }

            return resStream;
        }

        // ******************************************************************
        private static void DebugPrintResources(System.Resources.ResourceReader reader)
        {
            Debug.Print("Begin dump resources: ---------------------");
            foreach (DictionaryEntry item in reader)
            {
                Debug.Print(item.Key.ToString());
            }
            Debug.Print("End   dump resources: ---------------------");
        }

        // ****************************************************************** 
        #endregion
    }
}
